package org.geektimes.web.mvc.header;

import org.apache.commons.lang.StringUtils;
import org.geektimes.base.util.ReflectionUtils;
import org.geektimes.web.mvc.header.annotation.*;
import org.geektimes.web.mvc.header.matedata.HandlerMethodInfo;
import org.geektimes.web.mvc.header.matedata.ParamInfo;
import org.geektimes.web.mvc.header.matedata.WebContent;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

import static java.util.Arrays.asList;
import static org.apache.commons.lang.StringUtils.substringAfter;

/**
* mvc支持  
* @author : KangNing Hu
*/
public class WebMvcSupport {



	private final static WebControllerLoader WEB_CONTROLLER_LOADER;

	private static final String[] HTTP_METHODS = {HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.OPTIONS};

	/**
	 * 序列化器
	 */
	private ResultObjectSerializable resultObjectSerializable = new FastJsonResultObjectSerializable();


	static {
		//基于spi加载
		ServiceLoader<WebControllerLoader> serviceLoader = ServiceLoader.load(WebControllerLoader.class);
		Iterator<WebControllerLoader> iterator = serviceLoader.iterator();
		if (iterator.hasNext()){
			WEB_CONTROLLER_LOADER = iterator.next();
		}else {
			WEB_CONTROLLER_LOADER = new DefaultFileObjectWebControllerLoader();
		}
	}


	public void setResultObjectSerializable(ResultObjectSerializable resultObjectSerializable) {
		this.resultObjectSerializable = resultObjectSerializable;
	}


	/**
	 * 执行请求处理
	 * @param registry handler 注册器
	 * @param request 请求对象
	 */
	public void execute(HandlerMethodRegistry registry , ServletRequest request , ServletResponse response) throws ServletException, IOException {
		if (!(request instanceof HttpServletRequest) || !(response  instanceof HttpServletResponse)){
			return;
		}
		FrontControllerServlet servlet = (FrontControllerServlet) registry;
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		HttpServletResponse httpServletResponse = (HttpServletResponse) response;
		String path = getPath(httpServletRequest);
		HandlerMethodInfo handlerMethodInfo = registry.get(registry.getHandlerMethodKey(httpServletRequest , path));
		if (handlerMethodInfo != null){
			Set<String> supportedHttpMethods = handlerMethodInfo.getSupportedHttpMethods();
			// HTTP 方法不支持
			if (!supportedHttpMethods.contains(httpServletRequest.getMethod())){
				httpServletResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
				return;
			}
			Object result = null;
			String error = null;
			//方法调用
			try {
				result = handlerMethodInfo.invoke(new WebContent(httpServletRequest , httpServletResponse, registry));
			}catch (Throwable throwable){
				throwable.printStackTrace();
				///TODO 做全局异常捕获器
				Throwable cause = throwable.getCause();
				if (cause instanceof InvocationTargetException){
					Throwable cause1 = cause.getCause();
					error = cause1.getMessage();
				}else {
					error = cause.getMessage();
				}
			}
			//发生异常时，直接响应 500
			if (error != null){
				write(error , httpServletResponse , HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
				return;
			}
			//获取响应类型
			HandlerMethodInfo.ResponseType responseType = handlerMethodInfo.getResponseType();
			//处理body响应类型
			if (responseType == HandlerMethodInfo.ResponseType.JSON){
				if (result == null){
					result = new Object();
				}
				//写出响应 200
				write(resultObjectSerializable.serializable(result) ,httpServletResponse , HttpServletResponse.SC_OK);
			}
			//处理视图响应类型
			else {
				String view = result.toString();
				if (!view.endsWith(".jsp")){
					throw new IllegalStateException("不支持的响应视图类型：" + view);
				}
				view = servlet.getJspPrefix() + view;
				ServletContext servletContext = httpServletRequest.getServletContext();
				String rootPath = servletContext.getContextPath();
				rootPath = StringUtils.isEmpty(rootPath) ? "/" : rootPath;
				if (!view.startsWith(rootPath) ){
					view = rootPath + view;
				}
				servletContext.getRequestDispatcher(view).forward(request, response);
			}
		}else {
			//如果没有处理方法则分发给默认servlet处理
			RequestDispatcher requestDispatcher = httpServletRequest.getServletContext().getNamedDispatcher("default");
			requestDispatcher.forward(request , response);
		}
	}




	/**
	 * 加载方法处理器
	 * @param registry
	 */
	public void loadHandlerMethods(HandlerMethodRegistry registry){
		for (Object controller : WEB_CONTROLLER_LOADER.loaders()){
			doLoadHandlerMethods(controller , registry);
		}
	}


	/**
	 * 获取请求path
	 * @param request
	 * @return
	 */
	private String getPath(HttpServletRequest request){
		// 建立映射关系
		// requestURI = /a/hello/world
		String requestURI = request.getRequestURI();
		// contextPath  = /a or "/" or ""
		String prefixPath = request.getContextPath();
		// 映射路径（子路径）
		return substringAfter(requestURI, StringUtils.replace(prefixPath, "//", "/"));
	}


	/**
	 * 写出响应流
	 * @param result
	 */
	private void write(String result , HttpServletResponse response , int statusCode) throws IOException {
		response.setStatus(statusCode);
		response.setContentType("application/json");
		response.setCharacterEncoding("UTF-8");
		PrintWriter writer = response.getWriter();
		writer.write(result);
		response.flushBuffer();
	}

	/**
	 * 执行加载流程
	 * @param controller
	 * @param registry
	 */
	private void doLoadHandlerMethods(Object controller, HandlerMethodRegistry registry) {
		Class<?> aClass = controller.getClass();
		Path path = aClass.getAnnotation(Path.class);
		ResponseBody responseBody = aClass.getAnnotation(ResponseBody.class);
		//当有Path注解标记才视为web 控制器
		if (path == null){
			return;
		}
		String rootPath = path.value();
		boolean isResponseJson =  responseBody != null;
		//遍历public 修饰的 方法
		List<Method> methods = ReflectionUtils.getMethods(aClass, Object.class);
		for (Method method : methods){
			if (isWebMethod(method)){
				final Path pathAnnotation = ReflectionUtils.getAnnotation( method, Path.class);
				final ResponseBody responseBodyAnnotation = ReflectionUtils.getAnnotation(method , ResponseBody.class);
				HandlerMethodInfo handlerMethodInfo = new HandlerMethodInfo();
				handlerMethodInfo.setHandlerMethod(method);
				handlerMethodInfo.setTarget(controller);
				handlerMethodInfo.setSupportedHttpMethods(findSupportedHttpMethods(method));
				String servletPath = rootPath + pathAnnotation.value();
				handlerMethodInfo.setRequestPath(StringUtils.endsWith(servletPath , "/") ? servletPath : "/" + servletPath);
				handlerMethodInfo.setResponseType(isResponseJson ? HandlerMethodInfo.ResponseType.JSON :(responseBodyAnnotation != null ? HandlerMethodInfo.ResponseType.JSON : HandlerMethodInfo.ResponseType.VIEW ));
				//解析方法参数
				Type[] parameterTypes = method.getGenericParameterTypes();
				Annotation[][] parameterAnnotations = method.getParameterAnnotations();
				List<ParamInfo> paramInfos = new ArrayList<>();
				for (int i = 0 , length = parameterTypes.length ; i < length ; i ++){
					ParamInfo paramInfo = new ParamInfo(parameterTypes[i], i , parameterAnnotations[i] );
					paramInfos.add(paramInfo);
				}
				handlerMethodInfo.setParamInfo(paramInfos);
				registry.registerHandlerMethod(handlerMethodInfo);
			}
		}
	}

	/**
	 * 获取处理方法中标注的 HTTP方法集合
	 *
	 * @param method 处理方法
	 * @return
	 */
	private Set<String> findSupportedHttpMethods(Method method) {
		List<HttpMethod> annotations = ReflectionUtils.getAnnotations(method, HttpMethod.class);
		Set<String> supportedHttpMethods = annotations.stream().map(HttpMethod::value).collect(Collectors.toSet());
		if (supportedHttpMethods.isEmpty()) {
			supportedHttpMethods.addAll(asList(HTTP_METHODS));
		}
		return supportedHttpMethods;
	}


	/**
	 * 判断是否是web方法
	 * @param method
	 * @return
	 */
	private boolean isWebMethod(Method method){
		return ReflectionUtils.isAnnotation(method , Path.class);
	}



}
